/*
 * 
 */
package edu.gmu.javalution.pfronts;

import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Set;

/**
 *
 * @author jason.handuber
 */
public class ParetoFront<VEC, VEC_COMPARER extends Comparator<VEC>>
{

    public enum Dominance
    {

        DOMINATES, INCOMPARABLE, DOMINATEDBY
    };
    final VEC_COMPARER comparer;
    final Set<VEC> vectors;

    public ParetoFront(VEC_COMPARER comparer, VEC initialVector)
    {
        this.comparer = comparer;
        vectors = new HashSet<VEC>();
        this.vectors.add(initialVector);
    }

    public ParetoFront(VEC_COMPARER comparer, Collection<VEC> initialVectors)
    {
        assert (!initialVectors.isEmpty());
        this.comparer = comparer;
        vectors = new HashSet<VEC>(initialVectors);
    }

    /**
     * Note: do not modify the returned collection
     * @return
     */
    public Collection<VEC> getVectors()
    {
        return vectors;
    }

    /**
     * @param externalVec
     * @return -1 if this front dominates {@code externalVec}, 0 if it is
     * incomprable, 1 if this front is dominates by {@code externalVec}, and
     * -42 if {@code externalVec} is both incomparable and dominates this front.
     * The -42 code implies that {@code externalVec} <b>might</b> belong to
     * this front, or it might belong to a preceding front.
     */
    public int compareParetoTo(VEC externalVec)
    {
        /**
         * 3 possibilities:
         * (1) vector dominates all
         *      - easy case: return DOMINATES
         * (2) vector dominates some
         *      - hard case,
         *          - individuals can either be forced back or forward into
         *            another pareto front.
         * (3) vector dominated by all
         *      - easy case: return DOMINATED
         **/
        boolean iWin = false, iLose = false, iTie = false;
        for (VEC myVec : vectors)
        {
            int comparisonResult = comparer.compare(externalVec, myVec);
            // TIE (incomparable)
            if (comparisonResult == 0)
            {
                iTie = true;
            }
            // I (myVec) LOSES (externalVec DOMINATES)
            else if (comparisonResult < 0)
            {
                iLose = true;
            }
            // I (myVec) WIN (externalVec is DOMINATEDBY)
            else
            {
                iWin = true;
            }
        }

        // if this front both ties and is dominated by externalVec,
        // then externalVec should join this front and the dominated
        // individuals should move back
        if (iTie && iLose)
        {
            //TODO: should be -42
            return 0;
        }
        // How can one vector both dominates and be dominated by a piece-wise
        // created front
        if (iLose && iWin)
        {
            throw new RuntimeException("How can this happen?");
        }
        // if this front dominates externalVec
        else if (iWin)
        {
            return -1;
        }
        else if (iLose)
        {
            return 1;
        }
        else if (iTie)
        {
            return 0;
        }
        else
        {
            throw new RuntimeException("How can this happen, # my vecs: " +
                    vectors.size());
        }
    }

    public boolean add(VEC externalVec, Collection<VEC> displacedBackwards)
    {
        assert (displacedBackwards.isEmpty());
//        System.out.println("Seeing if I can add " + Arrays.toString((double[]) externalVec) + " to this front [" + super.toString() + "] that has " + vectors.size() + " vectors");
        if (vectors.contains(externalVec))
        {
            throw new IllegalArgumentException("Duplicate add attempt");
        }

        /**
         * 3 possibilities:
         * (1) vector dominates all
         *      - easy case: return DOMINATES
         * (2) vector dominates some
         *      - hard case,
         *          - individuals can either be forced back or forward into
         *            another pareto front.
         * (3) vector dominated by all
         *      - easy case: return DOMINATED
         **/
        boolean iTie = false, iWin = false, iLose = false;
        for (VEC myVec : vectors)
        {
            int comparisonResult = comparer.compare(externalVec, myVec);
            // TIE (incomparable)
            if (comparisonResult == 0)
            {
                iTie = true;
            }
            // I (myVec) LOSES (externalVec DOMINATES)
            else if (comparisonResult < 0)
            {
                iLose = true;
                displacedBackwards.add(myVec);
            }
            // I (myVec) WIN (externalVec is DOMINATEDBY)
            else
            {
                iWin = true;
            }
        }

        //if the new vector won against any vectors in this front, or tied
        //against all vectors in this front, it gets to join the front
        boolean added = false;
        if (iLose || iTie && !iWin)
        {
            added = vectors.add(externalVec);
        }
        //remove all displaced vectors, these are the losers
        this.vectors.removeAll(displacedBackwards);
        return added;
    }

    @Override
    public String toString()
    {
        String s = "{";
        for (VEC vec : vectors)
        {
            if (vec instanceof double[])
            {
                s += Arrays.toString(((double[]) vec)) + '\t';
            }
            else
            {
                s += vec.toString();
            }
        }
        s = s.trim();
        s += '}';
        return s;
    }
}
